#include "plst.h"
#include "bass.h"
#include "/windows/D/svn/preplayer/engine_cpp/adaptors.h"
#include "/windows/D/svn/preplayer/engine_cpp/pl_consts.h"
#include "ui_mainwindow.h"
#include <QtGui>
#include <QFileDialog>
#include <QtDebug>
#include <QVector>
#include <taglib/tbytevector.h>//ByteVector
#include <taglib/mpegfile.h>//mp3 file
#include <taglib/oggfile.h> //ogg
#include <taglib/flacfile.h>
#include <taglib/flacpicture.h>
#include <taglib/aifffile.h>
#include <taglib/wavfile.h>

#include <taglib/id3v2tag.h>//tag
#include <taglib/id3v2frame.h>//frame
#include <taglib/attachedpictureframe.h>
#include <taglib/tstring.h>
#include <cstdio>
#include <cstring>
#include <wchar.h>
#include <cstdlib>

//TODO ADD SUPPORT ALL TAGS
using namespace std;

const int CUE_TRACK1_PADDING = 0;

pl_tag id3v2_to_pltag(TagLib::Tag *tg);
char *tstring2char(TagLib::String str);
QString char2qs(TagLib::String str);


void plst::tgs_wgt_init(void)
{
    if ((tgs_wgt == NULL)||((*tgs_wgt) == NULL))
        return;
    if (!this->currentItem())
        return;
    //("Dimension of vector == %d", tgs_lst.size());
    //("current row == %d", this->currentRow());
    pl_tag tg = tgs_lst[this->currentRow()];
    for (int i = 0; i < (*tgs_wgt)->rowCount(); i++)
    {
        QTableWidgetItem *pitem = new QTableWidgetItem();

        QString txt;
        switch (i)
        {
        case 0: txt = tg.artist;
            break;
        case 1: txt = tg.title;
            break;
        case 2: txt = tg.album;
            break;
        case 3: txt.setNum(tg.year);
            break;
        case 4: txt = tg.genre;
            break;
        case 5: txt.setNum(tg.trackno);
            break;
        case 6: txt = tg.comment;
        }
        pitem->setText(txt);
        (*tgs_wgt)->setItem(i, 1, pitem);
    }
    (*tgs_wgt)->show();
    (*tgs_wgt)->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::MinimumExpanding);
}

plst::~plst()
{
    if (cover_image)
        delete cover_image;
    if (cover_scene)
        delete cover_scene;
}


void plst::cover_init(void)
{
    //cover_view = NULL;
    cover_image = NULL;
    cover_scene = NULL;
}


plst::plst(QWidget *widget, QGraphicsView **pixmap, QTableWidget **tags_wiget, QTimer *pl, QTimer *dr):QListWidget(widget)
{
    this->clear();
    this->setSelectionMode(QAbstractItemView::SingleSelection);
    this->setViewMode(QListView::ListMode);
    this->setDragEnabled(true);
    this->viewport()->setAcceptDrops(true);
    this->setDropIndicatorShown(true);
    this->setDragDropMode(QAbstractItemView::InternalMove);
    this->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
    this->setMinimumSize(628, 191);
    this->setMaximumSize(16777215, 16777215);
    cover_view = pixmap;
    tgs_wgt = tags_wiget;
    is_graphics_inload = false;
    pl_timer = pl;
    dr_timer = dr;
    cover_init();
}


pl_tag plst::get_at(int id)
{
    if (id >= 0 && id < tgs_lst.count()) {
        return tgs_lst[id];
    }
    else {
        perror("Bad index in query");
        abort();
    }
}

char * plst::qs2char(QString str) //non-copy fast-hack
{
    char *n_ptr = new char[1]; n_ptr[0] = 0;
    if (str.isNull()){qDebug("return \\0 because str is null");
        return n_ptr;}
    if (str.isEmpty()){qDebug("return \\0 because str is Empty");
        return n_ptr;}
    if (str.toLocal8Bit().isEmpty() || str.toLocal8Bit().isNull())
    {qDebug("return \\0 because str is or is NULL");
        return n_ptr;}

    //bs = str.toUtf8();
    //bs = str.toLocal8Bit();
    //bs = str.toLatin1();
    bs = str.toLocal8Bit();
    return bs.data();
}

QString plst::get_file_name(void)
{
    if (this->count() == 0)
        return NULL;
    if (!this->currentItem())
        this->setCurrentRow(0);
    return get_file_byid(this->currentRow());
}




QString plst::get_file_byid(int id)
{
    if ((id < 0)||(id >= this->count()))
        return NULL;
    this->setCurrentRow(id);
    tgs_wgt_init();
    slot_cover_update();
    return tgs_lst[id].fname;
}


//-----------------ADDITION PROCEDURES---------------------------------------------


void plst::delete_selected(void)
{
    qDebug("plst:: delete_selected");
    /*for (QVector::iterator it = tgs_lst.begin(); it != tgs_lst.end(); it++) {

    }*/
    qDebug("plst:: delete_selected end");
    // will be developed later
}

static int get_length(char * fname)
{
    HSTREAM hndl = engine_channel_create(fname, true);
    unsigned res;
    if (!hndl)
    {
        perror("Can`t create stream");
        fprintf(stderr, "%s \n", fname);
        fprintf(stderr, "ERR %d \n", BASS_ErrorGetCode());
        abort();
    }
    else
        res = floor(engine_channel_getlength(hndl)) + 1;
    engine_channel_terminate(hndl);
    return res;
}



void plst::add_fileqs(QString fname)
{
    QListWidgetItem *item = new QListWidgetItem(fname, this);
    item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled);
    TagLib::MPEG::File mp3file(qs2char(fname));
    TagLib::ID3v2::Tag *mp3tag = NULL;
    if (mp3file.isValid())
        mp3tag = mp3file.ID3v2Tag();
    pl_tag tg;
    if (mp3tag)
        tg = id3v2_to_pltag(mp3tag);//copy base tags
    if (tg.title.isEmpty() || tg.title.isNull())
    {
        QFileInfo finfo(fname);
        tg.title = finfo.fileName();
    }
    tg.fname = fname;
    tg.startpos = 0;
    tg.duration = get_length(qs2char(fname));
    tgs_lst.append(tg);
    item->setText(tg.title);
}

void plst::add_files(QStringList *lst)
{
    for (int i = 0; i < lst->count(); i++)
        add_fileqs(lst->at(i));
}

//--------------------------------------SLOTS---------------------------------------------
void plst::slot_file_add(void)
{
    QStringList *filenames = new QStringList;
    *filenames = QFileDialog::getOpenFileNames(this, tr("Open Audio File"), tr("/home"), tr("Audio Files (*.mp3 *.ogg *.wav *.mp2 *.mid *.midi *.ape *.flac)"));
    QDesktopServices::storageLocation(QDesktopServices::MusicLocation);
    if (filenames->count() != 0)
        this->add_files(filenames);
    this->activateWindow();
    delete filenames;
}


void plst::slot_file_open(void)
{
    pl_timer->stop();
    dr_timer->stop();
    this->clear();
    tgs_lst.clear();
    this->slot_file_add();
}

#ifdef WIN32
const char slash = '\\';
#else
const char slash = '/';
#endif

void plst::slot_dir_add(void)
{
    QString path = QFileDialog::getExistingDirectory(this, tr("Open Directory"), "/home",
                                                     QFileDialog::ShowDirsOnly
                                                     | QFileDialog::DontResolveSymlinks);
    if (path.isNull()) return;
    QDir dir(path);
    if (dir.exists())
    {
        QStringList lst = dir.entryList(QStringList() << "*.mp3"  << "*.ogg" << "*.wav" << "*.mp2" << "*.midi" << "*.mid", QDir::Files);
        for (int i = 0; i < lst.count(); i++)
            lst[i] = path + slash + lst[i];
        this->add_files(&lst);
    }
    this->show();
}

void plst::slot_dir_open(void)
{
    pl_timer->stop();
    dr_timer->stop();
    this->clear();
    tgs_lst.clear();
    this->slot_dir_add();
}

bool check_unicode(QString &filename)
{
    if (filename.isEmpty() || filename.isNull())
        return false;
    return filename.toLower().endsWith(".m3u8", Qt::CaseInsensitive);
}

static bool check_cue(QString &filename)
{
    if (filename.isEmpty() || filename.isNull())
        return false;
    return filename.toLower().endsWith(".cue", Qt::CaseInsensitive);
}

static QString cue_get(QFile * file, const QString &str, quint64 pos)
{
    file->seek(pos);
    static QByteArray ba;
    ba.clear();
    while (!file->atEnd() && (ba.indexOf(str) != 0) /*&& ba.simplified().isEmpty()*/)
    {
        ba = file->readLine().trimmed();
    }

    if (ba.indexOf(str) != 0) {
        return "";
    }
    QString res = QString::fromLocal8Bit(ba);
    res.remove(str);
    res = res.trimmed();
    return res;
}

static int cue_get_time(QString &str)
{
    int res  = 0;
    QString z = str.trimmed();
    res += z.left(2).toInt() * 60;
    z.remove(0, 3);
    res += z.left(2).toInt();
    return res;
}


void plst::load_cue(QString &filename)
{
    QFile in(filename);
    if (!in.open(QIODevice::ReadOnly | QIODevice::Text))
    {
        QMessageBox::information(NULL, "Can`t open file for reading", in.errorString());
        return;
    }
    pl_tag tg = {0, 0, 0, 0, 0, "", "", "", "" ,"" ,""};
    tg.album = cue_get(&in, "TITLE", 0);
    if (tg.album.isEmpty())
    {
        perror("Cue sheet is broken\n");
        return;
    }
    tg.album.remove("\"");
    tg.album = tg.album.trimmed();


    tg.fname = cue_get(&in, "FILE", 0);
    if (tg.fname.isEmpty())
    {
        perror("Cue sheet is broken\n");
        return;
    }
    if (tg.fname.endsWith("WAVE")) {
        tg.fname.remove("WAVE");
    } else if (tg.fname.endsWith("MP3")) {
         tg.fname.remove("MP3");
    } else if (tg.fname.endsWith("AIFF")) {
        tg.fname.remove("AIFF");
    } else if (tg.fname.endsWith("BINARY")) {
        tg.fname.remove("BINARY");
    } else if (tg.fname.endsWith("MOTOROLLA")) {
       tg.fname.remove("MOTOROLLA");
    }

    tg.fname.remove("\"");

    tg.fname = tg.fname.trimmed();
    QFileInfo info(filename);
    tg.fname  =  info.absoluteDir().absolutePath() +  xslash + tg.fname;
    info.setFile(tg.fname);
    if (!info.exists()) {
        fprintf(stderr, "BAYDA");
    }

    bool is_dirty = false, is_first = true;

    quint64 pos = in.pos();
    while (!in.atEnd())
    {
        QString str = cue_get(&in, "TRACK", pos);
        if (!str.isEmpty() && str.indexOf("AUDIO") != -1)
        {
            str.remove("AUDIO");
            pos = in.pos();
            str = str.trimmed();
            if (is_dirty)
            {
                if (!is_first)
                {
                    tgs_lst.last().duration = (tg.pad_pos) ? (tg.pad_pos -  tgs_lst.last().startpos) :
                                                             tg.startpos - tgs_lst.last().startpos;
                }
                is_first = false;
                tgs_lst.append(tg);
                QListWidgetItem *item = new QListWidgetItem(tg.title, this);
                is_dirty = false;
            }
            bool ok;
            tg.trackno = str.toInt(&ok);
            if (!ok)
                continue;
            str = cue_get(&in, "TITLE", pos);
            if (!str.isEmpty())
            {
                str.remove("\"");
                str = str.trimmed();
                if (!str.isEmpty())
                    tg.title = str;
            } else
                continue;


            str = cue_get(&in, "ARRANGER", pos);
            if (!str.isEmpty())
            {
                str.remove("\"");
                str = str.trimmed();
                if (!str.isEmpty())
                    tg.artist = str;
            } else
            {
                str = cue_get(&in, "PERFORMER", pos);
                if (!str.isEmpty())
                {
                    str.remove("\"");
                    str = str.trimmed();
                    if (!str.isEmpty())
                        tg.artist = str;
                }
            }

            str = cue_get(&in, "INDEX", pos);
            if (!str.isEmpty())
            {
                str = str.trimmed();
                if (str.startsWith("00"))
                {
                    str.remove("00");
                    tg.pad_pos = cue_get_time(str);
                    str = cue_get(&in, "INDEX", in.pos());
                    if (!str.isEmpty() && str.startsWith("01")) {
                        str.remove("01");
                        tg.startpos = cue_get_time(str);
                    }
                } else if (str.startsWith("01"))
                {
                    str.remove("01");
                    tg.pad_pos = 0;
                    tg.startpos = cue_get_time(str) + CUE_TRACK1_PADDING;
                }
            } else
                continue;
            is_dirty = true;
        }
    }

    if (is_dirty)
    {
        tgs_lst.last().duration = (tg.pad_pos) ? (tg.pad_pos -  tgs_lst.last().startpos) :
                                                 tg.startpos - tgs_lst.last().startpos;

        tg.duration = (tg.pad_pos) ?  (get_length(qs2char(tg.fname)) - tg.pad_pos) : (get_length(qs2char(tg.fname)) - tg.startpos);
        tgs_lst.append(tg);
        QListWidgetItem *item = new QListWidgetItem(tg.title, this);
    }
    // debug print
    char * l_name = "fname", *l_start = "start", *l_d = "duration";
    fprintf(stderr, "%8s %8s %8s\n", l_name, l_start, l_d);
    for (int i = 0; i < tgs_lst.count(); i++) {
        fprintf(stderr, "%8s %08d %08d\n", qs2char(tgs_lst[i].fname), tgs_lst[i].startpos, tgs_lst[i].duration);
    }
}

void plst::load_m3u(QString &filename)
{
    QFile in(filename);
    if (!in.open(QIODevice::ReadOnly | QIODevice::Text))
    {
        QMessageBox::information(NULL, "Can`t open file for reading", in.errorString());
        return;
    }
    while (!in.atEnd())
    {
        QString str;
        QByteArray ba = in.readLine();
        if (check_unicode(filename))
            str = QString::fromUtf8(ba);
        else
            str = QString::fromLocal8Bit(ba);
        str = str.trimmed();
        if (str[0] != '#')
            add_fileqs(str);
    }
    in.close();
}

void plst::slot_playlist_add(void)
{
    QString filename = QFileDialog::getOpenFileName(this, tr("Open Playlist"), tr("/"), tr("Playlist files (*.m3u *.m3u8 *.cue)"));
    if (!filename.isNull())
    {
        if (!check_cue(filename))
            load_m3u(filename);
        else
            load_cue(filename);


    }
    this->show();
}

void plst::slot_playlist_open(void)
{
    pl_timer->stop();
    dr_timer->stop();
    this->clear();
    tgs_lst.clear();
    slot_playlist_add();
}

void plst::slot_playlist_save(void)
{
    QString filename = QFileDialog::getSaveFileName(this, tr("Save Playlist"), tr("/"), tr("Playlist files (*.m3u *.m3u8)"));
    if (!filename.isNull())
    {
        QFile out(filename);
        if (!out.open(QIODevice::WriteOnly | QIODevice::Text))
        {
            QMessageBox::information(NULL, "ERROR", out.errorString());
            return;
        }
        for (int i = 0; i < tgs_lst.size(); i++)
        {
            QString str = tgs_lst[i].fname;
            QByteArray s1 = str.toUtf8();
            out.write(s1);
            out.write("\n");
        }
        out.close();
    }
}
//------------------------------SLOTS-TAGS-----------------------------------------------

void plst::slot_tagwidget_update(void)
{
    tgs_wgt_init();
}

void plst::slot_cover_update(void)
{
    if ((cover_view == NULL)|| ((*cover_view) == NULL))
        return;

    /*if (!this->currentItem())
        this->setCurrentRow(0);
    char *fname = this->QString2char(tgs_lst[this->currentRow()].fname);

    TagLib::ID3v2::Tag *mp3tag = get_tag(fname);
    if (mp3tag)
    {
        TagLib::ID3v2::FrameList fmp3_list = mp3tag->frameListMap()["APIC"];
        //if (!fmp3_list.isEmpty())
        {
            for (TagLib::ID3v2::FrameList::ConstIterator i = fmp3_list.begin(); i != fmp3_list.end();i++)
            {
                QFile::remove("/tmp/cover.jpg");
                TagLib::ID3v2::AttachedPictureFrame *frm = static_cast<TagLib::ID3v2::AttachedPictureFrame *> (*i);
                FILE *fout = fopen("/tmp/cover.jpg", "wb");
                fwrite(frm->picture().data(), frm->picture().size(), 1, fout);
                fclose(fout);
                QPixmap mp;
                mp.load("/tmp/cover.jpg");
                if (!cover_scene)
                    cover_scene = new QGraphicsScene;

                (*cover_view)->setScene(cover_scene);
                cover_scene->addPixmap(mp);
                (*cover_view)->show();
            }
        }
    }*/
    if (!this->currentItem())
        this->setCurrentRow(0);
    char *fname = qs2char(this->tgs_lst[this->currentRow()].fname);
    TagLib::MPEG::File mp3file(qs2char(fname));
    TagLib::ID3v2::Tag *mp3tag = mp3file.ID3v2Tag();
    if (!mp3tag)
        return;
    TagLib::ID3v2::FrameList fmp3_list = mp3tag->frameListMap()["APIC"];
    if (!fmp3_list.isEmpty())
    {
        for (TagLib::ID3v2::FrameList::ConstIterator i = fmp3_list.begin(); i != fmp3_list.end();i++)
        {
            QFile::remove("/tmp/cover.jpg");
            TagLib::ID3v2::AttachedPictureFrame *frm = static_cast<TagLib::ID3v2::AttachedPictureFrame *> (*i);
            FILE *fout = fopen("/tmp/cover.jpg", "wb");
            fwrite(frm->picture().data(), frm->picture().size(), 1, fout);
            fclose(fout);
            QPixmap mp;
            mp.load("/tmp/cover.jpg");
            if (!cover_scene)
                cover_scene = new QGraphicsScene;
            //if (!cover_view)
             //   cover_view = new QGraphicsView(cover_scene);
            (*cover_view)->setScene(cover_scene);
            cover_scene->addPixmap(mp);
            (*cover_view)->show();
        }
    }
}



///----------------------ADDITIONAL PROCEDURES-----------IMPLEMENTATION

TagLib::ID3v2::Tag *plst::get_tag(QString fname)
{
    TagLib::MPEG::File mpegfile(qs2char(fname));
    if (mpegfile.ID3v2Tag())
        return mpegfile.ID3v2Tag();
    return NULL;

}

QString char2qs(TagLib::String str)
{
    QString res;
    res = str.toCString(true);
    return res;
}

char *tstring2char(TagLib::String str)
{
    char *res = NULL;
    if ((!str.isEmpty()) && (!str.isNull()))
    {
        res = new char[str.size() + 2];
        strcpy(res, str.toCString(true));
    }
    return res;
}

char *plst::QString2char(QString str)//copied slow
{
    char *res = NULL;
    if ((!str.isEmpty()) && (!str.isNull()))
    {
        res = new char [str.size()];
        strcpy(res, qs2char(str));
    }
    return res;
}

pl_tag id3v2_to_pltag(TagLib::Tag *tg)
{
    pl_tag res;
    if (tg != NULL)
    {
        //res.album = tstring2char(tg->album());
        //res.artist = tstring2char(tg->artist());
        //res.title = tstring2char(tg->title());
        //res.comment = tstring2char(tg->comment());
        //res.genre = tstring2char(tg->genre());
        res.trackno = tg->track();
        res.album = tg->album().toCString(true);
        res.artist = tg->artist().toCString(true);
        res.title = tg->title().toCString(true);
        res.comment = tg->comment().toCString(true);
        res.genre = tg->genre().toCString(true);

    }
    return res;
}
